-module(umpire).
-behavior(gen_server).

-include_lib("eunit/include/eunit.hrl").

-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-compile(export_all).

start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
stop() -> gen_server:cast(?MODULE, stop).


add_player(Who) -> gen_server:call(?MODULE, {add, Who}).
use_card(PlayerId, CardIndex) -> gen_server:call(?MODULE, {use, PlayerId, CardIndex}).

init([]) -> 
	Tab = ets:new(?MODULE,[]),
	set_game_running(Tab, false),
	{ok, Tab}.

handle_call({use, PlayerId, CardIndex}, _From, Tab) ->
	Reply = case game_running(Tab) of
		true -> use_card_impl(PlayerId, CardIndex, Tab);
		false -> {error, "Game isn't started yet"}
	end,
	{reply, Reply, Tab};

handle_call({add,Who}, _From, Tab) ->
	Reply = case ets:lookup(Tab, player1) of
				[] -> insert_player(Tab, player1, Who);
				[_] -> case ets:lookup(Tab, player2) of
						[] -> 
							set_game_running(Tab, true),
							insert_player(Tab, player2, Who);
						[_] -> {error, "There are already 2 players"}
					  end
			end,
	{reply, Reply, Tab}.
	
use_card_impl(PlayerId, CardIndex, Tab) ->
	[{PlayerId, Player}] = ets:lookup(Tab, PlayerId),
	Cards = get_cards(Player),
	UsedCard = lists:nth(CardIndex, Cards),
	NewCards = lists:delete(UsedCard, Cards) ++ draw_cards(1),
	NewPlayer = replace_player(Tab, PlayerId, set_cards(Player, NewCards)),
	
	OpponentId = opponent(PlayerId),
	[{OpponentId, Opponent}] = ets:lookup(Tab, OpponentId),
	NewHp = get_hp(Opponent) - UsedCard,
	replace_player(Tab, OpponentId, set_hp(Opponent, NewHp)),
	
	set_game_running(Tab, NewHp > 0),
	NewPlayer.

	
handle_cast(_Msg, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

replace_player(Tab, PlayerId, Player) ->
	ets:delete(Tab, PlayerId),
	ets:insert(Tab, {PlayerId, Player}),
	Player.

insert_player(Tab, Id, Who) -> 
	Player = init_player(Id, Who),
	ets:insert(Tab, {Id, Player}),
	Player.

init_player(Id, Who) -> {Id, Who, 100, draw_cards(5)}.

opponent(player2) -> player1;
opponent(player1) -> player2.

draw_cards(0) -> [];
draw_cards(N) -> [random:uniform(10)] ++ draw_cards(N - 1).

get_cards({_, _, _, Cards}) -> Cards.
get_card(Player, Index) -> lists:nth(Index, get_cards(Player)).
get_hp({_, _, Hp, _}) -> Hp.

set_cards({PlayerId, Who, Hp, _}, NewCards) -> {PlayerId, Who, Hp, NewCards}.
set_hp({PlayerId, Who, _, Cards}, NewHp) -> {PlayerId, Who, NewHp, Cards}.

set_game_running(Tab, Running) ->
	ets:delete(Tab, running),
	ets:insert(Tab, {running, Running}).
game_running(Tab) -> 
	[{running, Running}] = ets:lookup(Tab, running),
	Running.

%% Tests

add_player_test_() ->
	{ok, Tab} = init([]),
	Player1 = andy,
	Player2 = bob,
	handle_call({add, Player1}, local, Tab),
	handle_call({add, Player2}, local, Tab),
	{reply, Error1, _} = handle_call({add, Player1}, local, Tab),
	{reply, Error2, _} = handle_call({add, Player2}, local, Tab),
	ErrorMessage = {error, "There are already 2 players"},
	[
	?_assertMatch([{player1, {player1, Player1, 100, _}}], ets:lookup(Tab, player1)),
	?_assertMatch([{player2, {player2, Player2, 100, _}}], ets:lookup(Tab, player2)),
	?_assertMatch(ErrorMessage, Error1),
	?_assertMatch(ErrorMessage, Error2),
	?_assertMatch(1, 1)
	].
	
init_player_test_() ->
	Who = andy,
	Id = player1,
	Player = init_player(Id, Who),
	[
	?_assertMatch({Id, Who, 100, [_, _, _, _, _]}, Player),
	?_assertMatch(1, 1)
	].
	
draw_cards_test_() ->
	[Card] = draw_cards(1),
	[
	?_assertMatch([_, _, _], draw_cards(3)),
	?_assert(Card >= 1),
	?_assert(Card =< 10)
	].
	
use_card_test_() ->
	{ok, Tab} = init([]),
	Name1 = andy,
	Name2 = bob,
	{reply, Player1, _} = handle_call({add, Name1}, local, Tab),
	{reply, Player2, _} = handle_call({add, Name2}, local, Tab),
	
	CardIndex = 1,
	handle_call({use, player1, CardIndex}, local, Tab),
	[{player1, NewPlayer1}] = ets:lookup(Tab, player1),
	[{player2, NewPlayer2}] = ets:lookup(Tab, player2),
	SecondCard = get_card(Player1, 2),
	ExpectedHp = get_hp(Player2) - get_card(Player1, 1),
	[
	?_assertMatch(SecondCard, get_card(NewPlayer1, 1)),
	?_assertMatch(ExpectedHp, get_hp(NewPlayer2)),
	?_assertMatch(1, 1)
	].
	
get_card_test_() ->
	Player = init_player(player1, andy),
	{_, _, _, [Card, _, _, _, _]} = Player,
	[
	?_assertMatch(Card, get_card(Player, 1))
	].
	
get_hp_test_() ->
	Player = init_player(player1, andy),
	{_, _, Hp, _} = Player,
	[
	?_assertMatch(Hp, get_hp(Player))
	].
	
find_opponent_id_test_() ->
	[
	?_assertMatch(player1, opponent(player2)),
	?_assertMatch(player2, opponent(player1))
	].
	
end_game_test_() ->
	{ok, Tab} = init([]),
	Name1 = andy,
	Name2 = bob,
	{reply, Player1, _} = handle_call({add, Name1}, local, Tab),
	handle_call({add, Name2}, local, Tab),
	replace_player(Tab, player1, set_hp(Player1, 1)),
	{reply, Winner, _} = handle_call({use, player2, 1}, local, Tab),
	[{player2, Player2}] = ets:lookup(Tab, player2),
	[
	?_assertMatch(false, game_running(Tab)),
	?_assertMatch(Player2, Winner),
	?_assertMatch(1, 1)
	].

running_status_test_() ->
	{ok, Tab} = init([]),
	set_game_running(Tab, true),
	[
	?_assert(game_running(Tab))
	].
	
cant_use_card_unless_game_running_test_() ->
	{ok, Tab} = init([]),
	{reply, Error, _} = handle_call({use, player1, 1}, local, Tab),
	[
	?_assertMatch({error, _}, Error)
	].
	